package cn.tedu._07springaop.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.sql.Array;
import java.util.Arrays;

/**
 * Aspect注解:标识该类为切面类;
 * Order注解:指定切面的优先级,数字越小,优先级越高;
 */

@Order(10)
@Aspect
@Component
public class LogAspect {
    /**
     Pointcut注解:定义切入点表达式方法的注解;
     */
    @Pointcut("execution(public int cn.tedu._07springaop.aop.CalculatorImpl.*(..))")
    public void doTime(){}

    /**
     * Before注解:
     *   1.前置通知,调用目标方法之前执行此方法;
     *   2.执行顺序,比如调用的是add方法
     *     2.1 框架优先检查是否定义了切面类[@Aspect];
     *     2.2 如果有,则检查是否定义了对应的切面方法[@Before];
     */
    //@Before("doTime()")
    //1.拦截所有包的所有类的所有方法;
    //@Before("execution(* * ..*.*(..))")
    //2.拦截指定的1个方法;
    //@Before("execution(public int cn.tedu._07springaop.aop.CalculatorImpl.add(int,int))")
    //3.拦截指定的2个方法,比如拦截 add 和 sub方法
    //@Before("execution(public int cn.tedu._07springaop.aop.CalculatorImpl.add(int,int)) || execution(public int cn.tedu._07springaop.aop.CalculatorImpl.sub(int,int))")
    //4.拦截指定类下的所有方法;
    //@Before("execution(public int cn.tedu._07springaop.aop.CalculatorImpl.*(..))")
    //5.粗粒度的切入点表达式：根据IoC容器中Bean对象的名称匹配;
    //@Before("bean(calculatorImpl)")
    //6.粗粒度的切入点表达式：根据包名和类名匹配;
    @Before("within(cn.tedu._07springaop.aop.*)")
    public void beforeMethod(JoinPoint joinPoint){
        /*
            1.joinPoint.getSignature().getName():获取目标方法名称;
            2.joinPoint.getArgs():获取目标方法参数;
         */
        String name = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());

        System.out.println("前置通知:" + name + "方法开始了,参数为:" + args);
    }

    /**
     * After注解:
     *   1.后置通知,目标方法彻底结束【正常结束 | 异常结束】后执行的方法;
     *   2.后置通知没有权限获取到目标方法的返回值;
     */
    @After("doTime()")
    public void afterMethod(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println("后置通知:" + name + "方法结束了");
    }

    /**
     * 3.AfterReturning注解:
     *   1.返回通知,目标方法正常返回后执行此方法;
     *   2.返回通知可以获取到目标方法的返回值;
     */
    @AfterReturning(value = "doTime()", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result){
        String name = joinPoint.getSignature().getName();
        System.out.println("返回通知:" + name + "方法正常返回,返回值为:" + result);
    }

    /**
     * 4.AfterThrowing注解:
     *   1.异常通知,目标方法异常结束,则执行此方法;
     *   2.异常通知可以获取到目标方法的异常信息;
     */
    @AfterThrowing(value = "doTime()", throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
        String name = joinPoint.getSignature().getName();
        System.out.println("异常通知:" + name + "方法异常结束,异常信息为:" + ex.getMessage());
    }

    /**
     * 5.Around注解:
     *   1.环绕通知,可在目标方法执行的各个阶段添加扩展逻辑;
     *   2.等价于: @Before + @After + @AfterReturning + @AfterThrowing;
     *   3.既可以获取目标方法的返回值,又可以获取异常对象;
     *   4.通常结合 try ... catch ... finally 使用;
     */
    @Around(value = "doTime()")
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
        Object result = null;
        /*
            正常结束: 1 2 3 5
            异常结束: 1 2 4 5
            try {
                1.前置通知;
                2.目标方法执行;
                3.返回通知;
            }catch(){
                4.异常通知;
            }finally{
                5.后置通知;
            }
         */
        try {
            System.out.println("环绕通知-前置通知");
            result = joinPoint.proceed();
            System.out.println("环绕通知-返回通知");
        } catch (Throwable e) {
            System.out.println("环绕通知-异常通知");
            throw new RuntimeException(e);
        } finally {
            System.out.println("环绕通知-后置通知");
        }
        return result;
    }
}








